/*
**  Copyright (c) 2014 Michael Liu(michael.liu.point@gmail.com).
**
**  Project: Gateway Unified Management Platform
**  File:      brifopt.c
**  Author:  Michael
**  Date:    06/27/2014
**
**  Purpose:
**   bridge interface method implement.
*/

/* Include files. */

#include "dpmgr.h"
#include "osdep.h"
#include "pfdep.h"
#include "brifopt.h"

/* Macro constant definitions. */

/* Type definitions. */

/* Local function declarations. */

static e_ret bridge_create(tdp_if * dp_if);
static e_ret bridge_destroy(tdp_if * dp_if);
static e_ret bridge_enable(tdp_if * dp_if);
static e_ret bridge_disable(tdp_if * dp_if);
static e_ret bridge_setlinkcfg(tdp_if * dp_if, void *linkcfg, tmask * mask);
static e_ret bridge_link(tdp_if * dp_if);
static e_ret bridge_unlink(tdp_if * dp_if);
static e_ret bridge_collectlinkstat(tdp_if * dp_if);
static e_ret bridge_getlinkstat(tdp_if * dp_if, void **linkstat, int *size, tmask * mask);
static e_ret bridge_getlinkstatus(tdp_if * dp_if, void **status, int *size, tmask * mask);
static e_ret bridge_disconnect(tdp_if * dp_if);
static e_ret bridge_collectnetstat(tdp_if * dp_if);
static e_ret bridge_getnetstat(tdp_if * dp_if, void **netstat, int *size, tmask * mask);
static e_ret bridge_getnetstatus(tdp_if * dp_if, void **status, int *size, tmask * mask);

/* Macro API definitions. */

/* Global variable declarations. */

e_ret bridge_if_prepare(tdp_if * dp_if)
{

	sysmgr_trace_enter();

	dp_if->create = bridge_create;
	dp_if->destroy = bridge_destroy;
	dp_if->enable = bridge_enable;
	dp_if->disable = bridge_disable;
	dp_if->setlinkcfg = bridge_setlinkcfg;
	dp_if->getlinkcfg = dpif_getlinkcfg_common;
	dp_if->link = bridge_link;
	dp_if->unlink = bridge_unlink;
	dp_if->collectlinkstat = bridge_collectlinkstat;
	dp_if->getlinkstat = bridge_getlinkstat;
	dp_if->getlinkstatus = bridge_getlinkstatus;
	dp_if->setnetcfg = dpif_setnetcfg_common;
	dp_if->getnetcfg = dpif_getnetcfg_common;
	dp_if->connect = dpif_connect_common;
	dp_if->disconnect = bridge_disconnect;
	dp_if->collectnetstat = bridge_collectnetstat;
	dp_if->getnetstat = bridge_getnetstat;
	dp_if->getnetstatus = bridge_getnetstatus;

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

e_ret bridge_set_uplink_if(tdata_paths * dp, tdp_if * br_if, const char *uplink_ifname)
{
	e_ret ret;
	int i;
	int first_idle;
	e_bool found;
	tdp_if *uplink_if;
	struct list_head *each;
	tbridge_if_linkcfg *br_linkcfg = (tbridge_if_linkcfg *) br_if->linkcfg;

	sysmgr_trace_enter();

	if (br_linkcfg == NULL) {
		sysmgr_trace_exit();
		return (eRET_INVALID_STATE);
	}

	found = eBOOL_FALSE;
	first_idle = DP_BR_MEMBER_MAX;
	for (i = 0; i < DP_BR_MEMBER_MAX; i++) {
		if (br_linkcfg->members[i][0] == 0) {
			if (first_idle == DP_BR_MEMBER_MAX) {
				first_idle = i;
			}
		} else if (strcmp(br_linkcfg->members[i], uplink_ifname) == 0) {
			found = eBOOL_TRUE;
		}
	}

	if ((!found) && (first_idle == DP_BR_MEMBER_MAX)) {
		sysmgr_trace_exit();
		return (eRET_NO_RESOURCE);
	}

	list_for_each(each, &dp->all_ifs) {
		uplink_if = list_entry(each, tdp_if, all_list);
		if (strcmp(uplink_if->name, uplink_ifname) == 0) {
			if (!found) {
				ret = bridge_add_member_if(dp, br_if, uplink_if);
				if (ret != eRET_SUCCESS) {
					sysmgr_dbg("bridge_add_member_if() " "failed, ret: %d.\n", ret);
					sysmgr_trace_exit();
					return (ret);
				}
				str_safe_cpy(br_linkcfg->members[first_idle], uplink_ifname, DP_IF_NAMESIZE - 1);
			}
			memset(br_linkcfg->uplink, 0, sizeof(br_linkcfg->uplink));
			str_safe_cpy(br_linkcfg->uplink, uplink_ifname, sizeof(br_linkcfg->uplink) - 1);
			br_if->data_if = uplink_if;
			sysmgr_trace_exit();
			return (eRET_SUCCESS);
		}
	}
	sysmgr_trace_exit();
	return (eRET_NOT_EXIST);
}

e_ret bridge_add_member_if(tdata_paths * dp, tdp_if * br_if, tdp_if * member_if)
{
	e_ret ret;

	sysmgr_dbg("dp_if[%s] add if[%s] to based_on_if.\n", br_if->name, member_if->name);

	ret = os_dep_add_bridge_member(br_if, member_if);
	if ((ret != eRET_SUCCESS) && (ret != eRET_ALREADY_EXIST)) {
		sysmgr_dbg("br[%s] add member if[%s] failed.\n", br_if->name, member_if->name);
		sysmgr_trace_exit();
		return (ret);
	}

	list_del(&member_if->hierarchy);
	INIT_LIST_HEAD(&member_if->hierarchy);
	list_add(&member_if->hierarchy, &br_if->based_on_if);

	if (mask_test_bit(member_if->attr, eDP_IF_ATTR_NET)) {
		mask_clear_bit(member_if->attr, eDP_IF_ATTR_NET);
	}

	member_if->data_if = br_if;
	return (eRET_SUCCESS);
}

e_ret bridge_remove_member_if(tdata_paths * dp, tdp_if * br_if, tdp_if * member_if)
{

	int i;
	tbridge_if_linkcfg *linkcfg;

	linkcfg = br_if->linkcfg;
	if (linkcfg == NULL) {
		return (eRET_INVALID_STATE);
	}

	list_del(&member_if->hierarchy);
	INIT_LIST_HEAD(&member_if->hierarchy);
	list_add(&member_if->hierarchy, &dp->net_ifs[member_if->dp_idx]);
	sysmgr_dbg("dp_if[%s] del if[%s] from based_on_if.\n", br_if->name, member_if->name);

	member_if->data_if = NULL;
	mask_set_bit(member_if->attr, eDP_IF_ATTR_NET);

	os_dep_del_bridge_member(br_if, member_if);

	for (i = 0; i < DP_BR_MEMBER_MAX; i++) {
		if (strcmp(linkcfg->members[i], member_if->name) == 0) {
			memset(linkcfg->members[i], 0, DP_IF_NAMESIZE);
			break;
		}
	}

	if (br_if->data_if == member_if) {
		br_if->data_if = NULL;
		if (linkcfg != NULL) {
			memset(linkcfg->uplink, 0, sizeof(linkcfg->uplink));
		}
	}
	return (eRET_SUCCESS);

}

e_ret bridge_create(tdp_if * dp_if)
{
	e_ret ret;

	sysmgr_trace_enter();

	ret = os_dep_create_bridge(dp_if);
	if ((ret != eRET_SUCCESS) && (ret != eRET_ALREADY_EXIST)) {
		sysmgr_trace_exit();
		return (ret);
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_destroy(tdp_if * dp_if)
{
	e_ret ret;

	sysmgr_trace_enter();

	ret = os_dep_destroy_bridge(dp_if);
	if ((ret != eRET_SUCCESS) && (ret != eRET_NOT_EXIST)) {
		sysmgr_trace_exit();
		return (ret);
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_enable(tdp_if * dp_if)
{
	e_ret ret;

	sysmgr_trace_enter();

	ret = os_dep_if_up(dp_if->name);

	sysmgr_trace_exit();
	return (ret);
}

e_ret bridge_disable(tdp_if * dp_if)
{
	e_ret ret;
	tdata_paths *dp;
	tdp_if *member_if;
	struct list_head *each, *next;

	sysmgr_trace_enter();

	dp = (tdata_paths *) dp_if->data_paths;

	/* Remove old members. */
	list_for_each_safe(each, next, &dp_if->based_on_if) {
		member_if = list_entry(each, tdp_if, hierarchy);
		bridge_remove_member_if(dp, dp_if, member_if);
	}

	if (dp_if->linkcfg) {
		memset(dp_if->linkcfg, 0, dp_if->linkcfg_size);
	}

	ret = os_dep_if_down(dp_if->name);

	sysmgr_trace_exit();
	return (ret);
}

e_ret bridge_setlinkcfg(tdp_if * dp_if, void *linkcfg, tmask * mask)
{

	int i;
	e_ret ret;
	e_bool found;
	tdp_if *member_if;
	tdata_paths *dp;
	struct list_head *each, *next;
	tbridge_if_linkcfg *br_linkcfg;
	tbridge_if_linkcfg *new_linkcfg;

	sysmgr_trace_enter();

	if (!mask_test_bitmask(mask->mask[0], eBRIDGE_IF_LINKCFG_MASK0_ALL)) {
		/* No update */
		sysmgr_trace_exit();
		return (eRET_SUCCESS);
	}

	if (dp_if->linkcfg == NULL) {
		br_linkcfg = malloc(sizeof(*br_linkcfg));
		if (br_linkcfg == NULL) {
			sysmgr_err("malloc(%d) failed, err: %d,[%s]\n", sizeof(*br_linkcfg), errno, strerror(errno));
			sysmgr_trace_exit();
			return (eRET_NO_RESOURCE);
		}
		memset(br_linkcfg, 0, sizeof(*br_linkcfg));
		dp_if->linkcfg = br_linkcfg;
		dp_if->linkcfg_size = sizeof(*br_linkcfg);
	} else {
		br_linkcfg = (tbridge_if_linkcfg *) dp_if->linkcfg;
	}

	dp = (tdata_paths *) dp_if->data_paths;
	new_linkcfg = (tbridge_if_linkcfg *) linkcfg;

	if (mask_test_bit(mask->mask[0], eBRIDGE_IF_LINKCFG_MASK0_MEMBERS)) {
		memcpy(&br_linkcfg->members, &new_linkcfg->members, sizeof(new_linkcfg->members));

		/* Add new members. */
		list_for_each(each, &dp->all_ifs) {
			member_if = list_entry(each, tdp_if, all_list);
			for (i = 0; i < DP_BR_MEMBER_MAX; i++) {
				if (new_linkcfg->members[i][0] == 0) {
					continue;
				} else if (strcmp(member_if->name, new_linkcfg->members[i]) == 0) {
					ret = bridge_add_member_if(dp, dp_if, member_if);
					if (ret != eRET_SUCCESS) {
						sysmgr_dbg("bridge_add_member_if() failed, ret: %d.\n", ret);
						sysmgr_trace_exit();
						return (ret);
					}
					break;
				}
			}
		}

		/* Remove old members. */
		list_for_each_safe(each, next, &dp_if->based_on_if) {
			member_if = list_entry(each, tdp_if, hierarchy);
			found = eBOOL_FALSE;

			for (i = 0; i < DP_BR_MEMBER_MAX; i++) {
				if (new_linkcfg->members[i][0] == 0) {
					continue;
				} else if (strcmp(member_if->name, new_linkcfg->members[i]) == 0) {
					found = eBOOL_TRUE;
					break;
				}
			}

			if (!found) {
				bridge_remove_member_if(dp, dp_if, member_if);
			}
		}
	}

	if (mask_test_bit(mask->mask[0], eBRIDGE_IF_LINKCFG_MASK0_UPLINK)) {
		ret = bridge_set_uplink_if(dp, dp_if, new_linkcfg->uplink);
		if (ret != eRET_SUCCESS) {
			sysmgr_dbg("bridge_add_member_if() failed, ret: %d.\n", ret);
			sysmgr_trace_exit();
			return (ret);
		}
	}

	sysmgr_trace_exit();
	return (eRET_SUCCESS);

}

e_ret bridge_link(tdp_if * dp_if)
{

	tdata_paths *dp;
	tdp_if *member_if;
	struct list_head *each, *next;

	sysmgr_trace_enter();

	if ((!mask_test_bit(dp_if->attr, eDP_IF_ATTR_LINK)) || (dp_if->linkcfg_size == 0) || (dp_if->linkcfg == NULL)) {
		sysmgr_trace_exit();
		return (eRET_INVALID_STATE);
	}

	dp = (tdata_paths *) dp_if->data_paths;

	/* make all member ifs linkup. */
	list_for_each_safe(each, next, &dp_if->based_on_if) {
		member_if = list_entry(each, tdp_if, hierarchy);
		dp_if_do_cmd(member_if, eDP_IF_METHOD_LINK, NULL);
	}

	dp_if->linkstatus = eDP_IF_LINK_STATUS_UP;

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_unlink(tdp_if * dp_if)
{

	tdata_paths *dp;
	tdp_if *member_if;
	struct list_head *each, *next;

	sysmgr_trace_enter();

	if (dp_if->linkstatus <= eDP_IF_LINK_STATUS_DOWN) {
		sysmgr_dbg("data path interface [%s] already unlinked.\n", dp_if->name);
		sysmgr_trace_exit();
		return (eRET_SUCCESS);
	}

	dp = (tdata_paths *) dp_if->data_paths;
	/* make all member ifs linkup. */
	list_for_each_safe(each, next, &dp_if->based_on_if) {
		member_if = list_entry(each, tdp_if, hierarchy);
		dp_if_do_cmd(member_if, eDP_IF_METHOD_UNLINK, NULL);
	}

	dp_if->linkstatus = eDP_IF_LINK_STATUS_DOWN;

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_collectlinkstat(tdp_if * dp_if)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_getlinkstat(tdp_if * dp_if, void **linkstat, int *size, tmask * mask)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_getlinkstatus(tdp_if * dp_if, void **status, int *size, tmask * mask)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_disconnect(tdp_if * dp_if)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_collectnetstat(tdp_if * dp_if)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_getnetstat(tdp_if * dp_if, void **netstat, int *size, tmask * mask)
{
	sysmgr_trace_enter();

	sysmgr_trace_exit();
	return (eRET_SUCCESS);
}

e_ret bridge_getnetstatus(tdp_if * dp_if, void **status, int *size, tmask * mask)
{
	e_ret ret;

	sysmgr_trace_enter();

	ret = dpif_getnetstatus_common(dp_if, (void *)status, size);

	sysmgr_trace_exit();
	return (ret);
}
